package dhome.watcher;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
 
import java.io.IOException;
import java.nio.file.ClosedWatchServiceException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

 
/**
 * 处理文件夹变化事件
 * @author ravi huang
 * @since java1.7
 */
public class DirectoryWatcher extends AbstractWatcher implements Runnable{	
	private static Logger log = LoggerFactory.getLogger(DirectoryWatcher.class);
	FileSystem fs = FileSystems.getDefault();
	WatchService ws;
	Map<WatchKey, Path> keys = new ConcurrentHashMap<>();
	ExecutorService service = Executors.newCachedThreadPool();		
	
	public DirectoryWatcher(){
		try {
			ws = fs.newWatchService();
		} catch (IOException e) {
			log.error(e.getLocalizedMessage());
		}		
	}
	
	@Override
	public void run() {	
		System.out.println("start:");
		while (true) {
			WatchKey key;
			try {
				key = ws.poll(10, TimeUnit.MILLISECONDS);
			} catch (InterruptedException | ClosedWatchServiceException e) {
				e.printStackTrace();
				break;
			}
			if (key != null) {
				Path path = keys.get(key);
				for (WatchEvent<?> i : key.pollEvents()) {
					WatchEvent<Path> event = cast(i);
					WatchEvent.Kind<Path> kind = event.kind();
					Path name = event.context();
					Path child = path.resolve(name);
					
					this.fire_event(WatcherEvent.FILECHANGED, path);
					//System.out.printf("%s: %s %s%n", kind.name(), path,	child);
					if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
						if (Files.isDirectory(child,
								LinkOption.NOFOLLOW_LINKS)) {
							try {
								walk(child, keys, ws);
							} catch (IOException e) {
								e.printStackTrace();
							}
						}
					}
				}
				if (key.reset() == false) {
					System.out.printf("%s is invalid %n", key);
					keys.remove(key);
					if (keys.isEmpty()) {
						break;
					}
				}
			}
		}
		System.out.println("end!");
	}
	public void start_watcher(){
		service.submit(this);
	}
	
	public void stop_watcher(){
		try {
			service.shutdown();
			ws.close();
		} catch (IOException e) {
			log.error(e.getLocalizedMessage());
		}
		service.shutdownNow();
	}
	
	public static void main(String[] args) throws Exception {
		DirectoryWatcher dw=new DirectoryWatcher(); 
		
		dw.start_watcher();
		dw.reg("F:\\tmp"); 
		int i=1000;
		while(i>0){
			i--;
			Thread.sleep(500);
		}
		dw.stop_watcher();
	}
	
 
	public void walk(Path root, final Map<WatchKey, Path> keys,
			final WatchService ws) throws IOException {
		Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
			@Override
			public FileVisitResult preVisitDirectory(Path dir,
					BasicFileAttributes attrs) throws IOException {
				reg(dir);
				return super.preVisitDirectory(dir, attrs);
			}
		});
	}
 
	private void reg(Path dir)
			throws IOException {
		WatchKey key = dir.register(ws, ENTRY_CREATE, ENTRY_DELETE,
				ENTRY_MODIFY);
		keys.put(key, dir);		
	}
	
	public WatchKey reg(String filename)
			throws IOException {
		Path dir=fs.getPath(filename);
		WatchKey key = dir.register(ws, ENTRY_CREATE, ENTRY_DELETE,
				ENTRY_MODIFY);
		keys.put(key, dir);
		return key;
	}
	@SuppressWarnings("unchecked")
	static <T> WatchEvent<T> cast(WatchEvent<?> event) {
		return (WatchEvent<T>) event;
	}
}